/*
 * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: CC0-1.0
 */

#include <stdio.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "driver/ledc.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_system.h"
#include "esp_heap_caps.h"
#include "esp_timer.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_new_panel_st7735.h"
#include "esp_lcd_backlight.h"
#include "chip_info.h"
#include "rotary_encoder.h"
#include "lvgl.h"
#include "t_radio.h"
#include "events.h"
#include "error_check.h"

// Конфигурация пинов для ENCODER'а
#define ENC_PIN_NUM_A                   GPIO_NUM_36
#define ENC_PIN_NUM_B                   GPIO_NUM_39
#define ENC_PIN_NUM_SW                  GPIO_NUM_34

// Конфигурация пинов для LCD TFT
#define LCD_PIN_NUM_MOSI                GPIO_NUM_23
#define LCD_PIN_NUM_PCLK                GPIO_NUM_18
#define LCD_PIN_NUM_CS                  GPIO_NUM_5
#define LCD_PIN_NUM_DC                  GPIO_NUM_17
#define LCD_PIN_NUM_RST                 GPIO_NUM_16
#define LCD_PIN_NUM_BACKLIGHT           GPIO_NUM_4

// Using SPI2 in the example, as it also supports octal modes on some targets
#define LCD_HOST                        SPI2_HOST

// To speed up transfers, every SPI transfer sends a bunch of lines. This define specifies how many.
// More means more memory use, but less overhead for setting up / finishing transfers. Make sure 240
// is dividable by this.
#define PARALLEL_LINES                  CONFIG_EXAMPLE_LCD_FLUSH_PARALLEL_LINES

// The number of frames to show before rotate the graph
#define ROTATE_FRAME                    30
#define LCD_PIXEL_CLOCK_HZ              (20 * 1000 * 1000)
#define LCD_BACKLIGHT_ON_LEVEL          1
#define LCD_BACKLIGHT_OFF_LEVEL         !LCD_BACKLIGHT_ON_LEVEL

// The pixel number in horizontal and vertical
#define LCD_H_RES                       160
#define LCD_V_RES                       128

// Bit number used to represent command and parameter
#define LCD_CMD_BITS                    8
#define LCD_PARAM_BITS                  8
#define LCD_TRANS_QUEUE_DEPTH           10

// LEDC
#define LEDC_TIMER                      LEDC_TIMER_0
#define LEDC_CHANNEL                    LEDC_CHANNEL_0

// LVGL
#define LV_TICK_PERIOD_MS               16

typedef struct {
    uint8_t volume_level;
    uint8_t backlight_brightness;
} t_radio_t;

typedef struct {
    lv_obj_t* popup;
    lv_obj_t* label;
    lv_timer_t* timer;
} volume_popup_t;

static rotary_encoder_handle_t s_rotary_encoder = NULL;
static esp_lcd_panel_handle_t s_panel_handle = NULL;
static esp_lcd_backlight_handle_t s_backlight = NULL;
static QueueHandle_t s_event_queue = NULL;
static TaskHandle_t s_display_task = NULL;
static lv_display_t* s_display = NULL;
static lv_color_t* s_primary_buffer = NULL;
static volume_popup_t* s_volume_popup = NULL;
static t_radio_t s_settings = {
    .volume_level = (T_RADIO_VOLUME_MAX - T_RADIO_VOLUME_MIN) >> 1,
    .backlight_brightness = 60,
};

static esp_err_t rotary_encoder_initialize(void);
static esp_err_t lcd_driver_initialize(void);
static esp_err_t display_start(void);
static void rotary_encoder_callback(const rotary_encoder_event_t* encoder_event, const void* user_context);
static void timer_tick_handler(void* arg);
static void display_flush_callback(lv_display_t* display, const lv_area_t* area, uint8_t* color_data);
static void display_task_routine(void* arg);
static void create_home_screen(void);
static void create_volume_popup(void);

static const char* TAG = "t_radio";

esp_err_t t_radio_create(QueueHandle_t event_queue)
{
#if CONFIG_T_RADIO_PRINT_CHIP_INFO == 1    
    RETURN_ON_ERROR(print_chip_info());
#endif

    //s_event_queue = xQueueGenericCreate(1, sizeof(event_t), queueQUEUE_TYPE_SET);
    s_event_queue = event_queue;

#if CONFIG_T_RADIO_USE_ENCODER == 1
    RETURN_ON_ERROR(rotary_encoder_initialize());
#else
    ESP_LOGI(TAG, "Does not use encoder");
#endif

    RETURN_ON_ERROR(lcd_driver_initialize());
    // Инициализация и запуск задачи обновления дисплея
    RETURN_ON_ERROR(display_start());

    return ESP_OK;
}

esp_err_t t_radio_get_volume_level(uint8_t* volume_level)
{
    ESP_RETURN_ON_FALSE(volume_level, ESP_ERR_INVALID_ARG, TAG, "volume_level");

    *volume_level = s_settings.volume_level;

    return ESP_OK;
}

esp_err_t t_radio_set_volume_level(const uint8_t volume_level)
{
    ESP_RETURN_ON_FALSE(T_RADIO_VOLUME_MIN <= volume_level && T_RADIO_VOLUME_MAX >= volume_level, ESP_ERR_INVALID_ARG, TAG, "volume_level");

    s_settings.volume_level = volume_level;

    create_volume_popup();
    ESP_LOGI(TAG, "set volume: %d", s_settings.volume_level);

    return ESP_OK;
}

static esp_err_t rotary_encoder_initialize(void)
{
    rotary_encoder_config_t config = {
        .gpio_encoder_a = ENC_PIN_NUM_A,
        .gpio_encoder_b = ENC_PIN_NUM_B,
        .event_callback = rotary_encoder_callback
    };

    return rotary_encoder_create(&config, &s_rotary_encoder);
}

static esp_err_t initialize_spi_bus(void)
{
    spi_bus_config_t spi_bus_config = {
        .sclk_io_num = LCD_PIN_NUM_PCLK,
        .mosi_io_num = LCD_PIN_NUM_MOSI,
        .miso_io_num = -1,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = PARALLEL_LINES * LCD_H_RES * 2 + 8
    };

#if CONFIG_EXAMPLE_LCD_SPI_8_LINE_MODE

#endif

    // Инициализация шины SPI
    return spi_bus_initialize(LCD_HOST, &spi_bus_config, SPI_DMA_CH_AUTO);
}

static esp_err_t initialize_lcd_backlight(void)
{
    // Конфигурация контроллера подсветки LCD панели
    esp_lcd_backlight_config_t bcklt_config = {
        .timer_idx = LEDC_TIMER,
        .channel_idx = LEDC_CHANNEL,
        .gpio_num = LCD_PIN_NUM_BACKLIGHT
    };
    // Инициализация контроллера подсветки LCD панели
    RETURN_ON_ERROR(esp_lcd_backlight_create(&bcklt_config, &s_backlight));

    // Выключение подсветки LCD панели для предотвращения непредвиденных эффектов
    return esp_lcd_backlight_set(s_backlight, 0);
}

static esp_err_t initialize_lcd_panel(void)
{
    // Конфигурация LCD панели на шине SPI
    esp_lcd_panel_io_handle_t io_handle = NULL;
    esp_lcd_panel_io_spi_config_t io_config = {
        .dc_gpio_num = LCD_PIN_NUM_DC,
        .cs_gpio_num = LCD_PIN_NUM_CS,
        .pclk_hz = LCD_PIXEL_CLOCK_HZ,
        .lcd_cmd_bits = LCD_CMD_BITS,
        .lcd_param_bits = LCD_PARAM_BITS,
        .spi_mode = 0,
        .trans_queue_depth = LCD_TRANS_QUEUE_DEPTH,
    };

#if CONFIG_EXAMPLE_LCD_SPI_8_LINE_MODE

#endif

    // Добавляем LCD панель на шину SPI
    ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));

    // Конфигурация LCD панели
    esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = LCD_PIN_NUM_RST,
        .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
        .bits_per_pixel = 16,
        .vendor_config = NULL,
    };

    // Создание драйвера LCD панели
    return esp_lcd_new_panel_st7735(io_handle, &panel_config, &s_panel_handle);
}

static esp_err_t lcd_driver_initialize(void)
{
    // Инициализация шины SPI
    RETURN_ON_ERROR(initialize_spi_bus());
    // Инициализация подсветки LCD панели
    RETURN_ON_ERROR(initialize_lcd_backlight());
    // Инициализация LCD панели на шине SPI
    RETURN_ON_ERROR(initialize_lcd_panel());
    // Сброс драйвера LCD панели перед инициализацией
    RETURN_ON_ERROR(esp_lcd_panel_reset(s_panel_handle));
    // Инициализация драйвера LCD панели
    RETURN_ON_ERROR(esp_lcd_panel_init(s_panel_handle));

#if CONFIG_LCD_ST7735_REVISION_R == 1
    // 
    ESP_ERROR_CHECK(esp_lcd_panel_set_gap(s_panel_handle, 26, 1));
#elif CONFIG_LCD_ST7735_REVISION_G == 1
    // 
    //ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel_handle, 2, 3));
#endif
    // Развернуть отображение на панели LCD
    RETURN_ON_ERROR(esp_lcd_panel_swap_xy(s_panel_handle, true));
    // Mirror LCD by vertical axis
    RETURN_ON_ERROR(esp_lcd_panel_mirror(s_panel_handle, true, false));
    // Включение LCD панели
    RETURN_ON_ERROR(esp_lcd_panel_disp_on_off(s_panel_handle, true));
    // Включение подсветки LCD панели
    return esp_lcd_backlight_set(s_backlight, s_settings.backlight_brightness);
}

static void rotary_encoder_callback(const rotary_encoder_event_t* encoder_event, const void* user_context)
{
    ((void)user_context);

    switch(encoder_event->direction)
    {
        case ROTARY_ENCODER_DIRECTION_CLOCKWISE:
        {
            event_t event = {
                .event_type = EVENT_TYPE_ENCODER,
                .generic_encoder = {
                    .action = ENCODER_ACTION_CW
                },
            };

            if (pdTRUE == xQueueGenericSend(s_event_queue, &event, portMAX_DELAY, queueSEND_TO_BACK))
            {
                ;
            }

            break;
        }

        case ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE:
        {
            event_t event = {
                .event_type = EVENT_TYPE_ENCODER,
                .generic_encoder = {
                    .action = ENCODER_ACTION_CCW
                },
            };

            if (pdTRUE == xQueueGenericSend(s_event_queue, &event, portMAX_DELAY, queueSEND_TO_BACK))
            {
                ;
            }
            
            break;
        }

        default: break;
    }
}

static esp_err_t display_start(void)
{
    // Создание таймера
    esp_timer_handle_t periodic_timer;
    const esp_timer_create_args_t periodic_timer_args = {
        .callback = timer_tick_handler,
        .name = "lvgl_tick_inc",
    };
    RETURN_ON_ERROR(esp_timer_create(&periodic_timer_args, &periodic_timer));
    RETURN_ON_ERROR(esp_timer_start_periodic(periodic_timer, LV_TICK_PERIOD_MS * 1000));

    // Инициализация библиотеки LVGL
    lv_init();

    const size_t primary_buffer_size = (LCD_H_RES * LCD_V_RES / 4) * sizeof(lv_color_t);
    
    s_primary_buffer = heap_caps_malloc(primary_buffer_size, MALLOC_CAP_DMA);
    s_display = lv_display_create(LCD_H_RES, LCD_V_RES);

    lv_display_set_buffers(s_display, s_primary_buffer, NULL, primary_buffer_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
    lv_display_set_flush_cb(s_display, display_flush_callback);
    
    create_home_screen();

    BaseType_t created = xTaskCreate(
        display_task_routine,
        "display_task",
        4096,
        NULL,
        tskIDLE_PRIORITY,
        &s_display_task
    );

    return ESP_OK;
}

static void timer_tick_handler(void* arg)
{
    ((void)arg);
    lv_tick_inc(LV_TICK_PERIOD_MS);
}

static void display_flush_callback(lv_display_t* display, const lv_area_t* area, uint8_t* color_data)
{
    ESP_ERROR_CHECK(
        esp_lcd_panel_draw_bitmap(
            s_panel_handle,
            area->x1,
            area->y1,
            area->x2 + 1,
            area->y2 + 1,
            color_data
        )
    );
    ESP_ERROR_CHECK(esp_lcd_new_panel_st7735_flush(s_panel_handle));

    lv_display_flush_ready(display);
}

static void display_task_routine(void* arg)
{
    ((void)arg);

    // Запуск цикла для работы библиотеки LVGL
    SemaphoreHandle_t sem = xSemaphoreCreateMutex();

    while(true)
    {
        vTaskDelay(pdMS_TO_TICKS(LV_TICK_PERIOD_MS));

        if (pdTRUE == xSemaphoreTake(sem, portMAX_DELAY))
        {
            lv_task_handler();
            xSemaphoreGive(sem);
        }
    }
}

static void _anim_x_cb(void* var, int32_t v)
{
    lv_obj_set_x(var, v);
    //lv_obj_set_y(var, v);
}

static void _anim_size_cb(void* var, int32_t v)
{
    lv_obj_set_size(var, v, v);
}

static void create_home_screen(void)
{
    lv_obj_t* screen = lv_display_get_screen_active(s_display);
    lv_obj_t* label1 = lv_label_create(screen);
    lv_label_set_text(label1, "Hello\nworld");
    lv_obj_align(label1, LV_ALIGN_CENTER, 0, 0);

    lv_obj_t* obj = lv_obj_create(screen);
    lv_obj_set_style_bg_color(obj, lv_palette_main(LV_PALETTE_GREEN), 0);
    lv_obj_set_style_radius(obj, LV_RADIUS_CIRCLE, 0);

    lv_obj_align(obj, LV_ALIGN_LEFT_MID, 0, 0);
    //lv_obj_align(obj, LV_ALIGN_TOP_MID, 0, 10);

    lv_anim_t a;
    lv_anim_init(&a);
    lv_anim_set_var(&a, obj);
    lv_anim_set_values(&a, 10, 30);
    lv_anim_set_duration(&a, 1000);
    lv_anim_set_playback_delay(&a, 100);
    lv_anim_set_playback_duration(&a, 300);
    lv_anim_set_repeat_delay(&a, 500);
    lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
    lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out);

    lv_anim_set_exec_cb(&a, _anim_size_cb);
    lv_anim_start(&a);
    lv_anim_set_exec_cb(&a, _anim_x_cb);
    lv_anim_set_values(&a, 0, LCD_H_RES - 30);

    lv_anim_start(&a);
}

static void volume_popup_callback(lv_timer_t* timer)
{
    ((void)timer);

    if (NULL != s_volume_popup)
    {
        //lv_timer_delete(s_volume_popup->timer);
        //lv_msgbox_close_async(s_volume_popup->popup);
        lv_msgbox_close(s_volume_popup->popup);
        lv_obj_delete(s_volume_popup->popup);
        heap_caps_free(s_volume_popup);

        s_volume_popup = NULL;
    }
}

static void create_volume_popup(void)
{
    char text[20];

    sprintf(text, "volume: %d", s_settings.volume_level);

    if (NULL == s_volume_popup)
    {
        s_volume_popup = (volume_popup_t*)heap_caps_calloc(1, sizeof(volume_popup_t), MALLOC_CAP_DEFAULT);

        lv_obj_t* screen = lv_display_get_screen_active(s_display);
        s_volume_popup->popup = lv_msgbox_create(screen);

        lv_obj_set_width(s_volume_popup->popup, LCD_H_RES - 40);
        lv_obj_set_align(s_volume_popup->popup, LV_ALIGN_CENTER);
        
        s_volume_popup->label = lv_msgbox_add_text(s_volume_popup->popup, text);        
        s_volume_popup->timer = lv_timer_create(volume_popup_callback, 5000, NULL);
        lv_timer_set_repeat_count(s_volume_popup->timer, 1);
        lv_timer_set_auto_delete(s_volume_popup->timer, true);
        lv_timer_resume(s_volume_popup->timer);
    }
    else
    {
        lv_label_set_text(s_volume_popup->label, text);
        lv_timer_reset(s_volume_popup->timer);
    }
}